/*
 *  Entity.cpp
 *  Oblivion++
 *
 *  Created by Billy Clack on 8/7/12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */

#include "Entity.h"
#include "PhysicsFactory.h"
#include "Displayer.h"
#include "GameManager.h"

//
// Game Entity
//
GameEntity::GameEntity(ENTITY_TYPE inType, GameManager * inMgr) : 
	mType(inType),
	mPhysPrim(NULL),
	mManager(inMgr) {}

void GameEntity::Update( float timeStep) {
}

PhysicsPrimitive * GameEntity::GetPhysicsPrimitive() { return mPhysPrim; }


//
// Ground game entity
//
GroundGameEntity::GroundGameEntity( glm::vec2 * glmverts, int numVerts, GameManager * mgr) :
	GameEntity(ENTITY_GROUND, mgr)
	{
		PhysicsProperties pp;
		pp.mOrigin = b2Vec2(0, 0);
		pp.mDensity = 1;
		pp.mFriction = 1;
		pp.mElastic = 0;
		pp.mIsStatic = true;
		
		std::vector<b2Vec2> verts;
		
		for (int i = 0; i < numVerts; ++i) {
			verts.push_back(b2Vec2(glmverts[i].x, glmverts[i].y));
		}
		
		mPhysPrim = PhysicsFactory::CreateChain(&(verts[0]), numVerts, pp );
		mPhysPrim->AttachGameEntity(this);
	
	}

void GroundGameEntity::AddVerts( glm::vec2 * verts, int numVerts )
{
	if (mPhysPrim->mShape->GetType() != b2Shape::e_chain) {
		return;
	}

	b2ChainShape * cShape = static_cast< b2ChainShape*> (this->mPhysPrim->mShape);
	
	for (int i = 0; i < numVerts; ++i) {
		cShape->SetNextVertex( b2Vec2(verts[numVerts].x, verts[numVerts].y ) );
	}
}

//
//PLayer game entity
//
PlayerGameEntity::PlayerGameEntity( 
	const PlayerGameEntityDef & inDef, 
	GameManager * mgr ) 
	: 
	GameEntity(ENTITY_PLAYER, mgr),
	mInputDirection( glm::vec2(0,0) ),
	mInputSpeed( 0 ),
	mMaxInputSpeed( inDef.maxInputSpeed ),
	mTimeSinceLastAttack( 0.0 ),
	mCurrentState(PLAYER_STATE_NONE),
	mCurrWeapon(NULL)
{
		
		mPhysPrim = PhysicsFactory::CreateCircle( //Make sure the real pos is set in mOrigin of physics properties
												 0.5,
												 inDef.pp );

}

glm::vec2 PlayerGameEntity::GetInputVelocity() {
	return (mInputDirection * mInputSpeed);
}

glm::vec2 PlayerGameEntity::GetTotalVelocity() {
	b2Vec2 linVel = mPhysPrim->GetLinearVelocity();
	glm::vec2 inVel = GetInputVelocity();
	return glm::vec2(linVel.x, linVel.y) + inVel;
}

void PlayerGameEntity::SetInputDirection(const glm::vec2 & inVec) {
	mInputDirection = inVec;
}

void PlayerGameEntity::IncrementInputSpeed( float speed ) {
	mInputSpeed += speed;
	if (mInputSpeed < 0) {
		mInputSpeed = 0;
	}
	if (mInputSpeed > mMaxInputSpeed) {
		mInputSpeed = mMaxInputSpeed;
	}

}

float PlayerGameEntity::GetMaxInputSpeed() {
	return mMaxInputSpeed;
}

void PlayerGameEntity::SetMaxInputSpeed( float inspeed ) {
	this->mMaxInputSpeed = inspeed;
}

//This updates the player's velocity based on the user-input controls.
void PlayerGameEntity::UpdateInputParams(float percentDecay) {

	if (CheckStateBit(PLAYER_STATE_WALKING)) {
		IncrementInputSpeed(0.1);
	}
	else {
		//updates the linear velocity.
		DecayInputSpeed(percentDecay);	
	}
	
	glm::vec2 totVel = GetTotalVelocity();
	this->mPhysPrim->SetLinearVelocity(b2Vec2(totVel.x, totVel.y));

}

void PlayerGameEntity::DecayInputSpeed ( float percent ) {
	//decays the input speed
	IncrementInputSpeed(-(mMaxInputSpeed * percent));

}

//Can have multiple states
void PlayerGameEntity::SetState( PLAYER_STATE_T inState) {
	this->mCurrentState = inState;
}

void PlayerGameEntity::SetStateBit( PLAYER_STATE_T inState) {
	mCurrentState |= inState;
}

void PlayerGameEntity::RemoveStateBit( PLAYER_STATE_T inState) {
	mCurrentState &= ~inState;
}

PLAYER_STATE_T PlayerGameEntity::GetState() {
	return this->mCurrentState;
}

bool PlayerGameEntity::CheckStateBit( PLAYER_STATE_T inState ){
	return mCurrentState & inState;
}

void PlayerGameEntity::SetTarget( const glm::vec2 & inTarget ){
	this->mTarget = inTarget;
}

void PlayerGameEntity::SetWeapon( WeaponGameEntity * inWeapon ){
	mCurrWeapon = inWeapon;
}

void PlayerGameEntity::Update(float timeStep ) {
	UpdateInputParams( 0.25 ); //Change to be input property XXX

	//Check if it is firing
	//If it can, attack with whatever weapon we are holding.
	if (CheckStateBit(PLAYER_STATE_SHOOTING)) {
		Attack();
	}
}

b2Vec2 PlayerGameEntity::GetPos() {
	return mPhysPrim->GetPos();
}

void PlayerGameEntity::Attack() {
	//ATTACK!
	if (mCurrWeapon == NULL)
	{
		return;
	}

	//Fire the player's weapon.
	glm::vec2 weaponOrigin = glm::vec2(GetPos().x, GetPos().y);
	glm::vec2 weaponTarget = glm::vec2(mTarget.x, mTarget.y);
	mCurrWeapon->Fire(weaponOrigin, weaponTarget);
}

WeaponGameEntity::WeaponGameEntity(const WeaponProperties & inProps, GameManager * inMgr)
	: GameEntity(ENTITY_WEAPON, inMgr),
	mProperties(inProps),
	mCurrFireCounter(0)
{
}

void WeaponGameEntity::Update( float timeStep )
{
	//Updates the current counter with the firing reate.
	mCurrFireCounter += timeStep;

	return;
}

bool WeaponGameEntity::IsTimeToFire() {
	return mCurrFireCounter >= mProperties.mFireRate;
}

void WeaponGameEntity::ResetFireCounter() {
	mCurrFireCounter = 0;
}

RaycastWeaponGameEntity::RaycastWeaponGameEntity( const WeaponProperties & inProps, GameManager * mgr )
	: WeaponGameEntity( inProps, mgr )
{
	return;
}

//This is a ray cast type, so they will all utilize a ray cast and perform isntant shot.
void RaycastWeaponGameEntity::Fire(const glm::vec2 & origin, const glm::vec2 & target ) {
	HitRec hr;
	if (!IsTimeToFire() ){
		return;
	}

	b2World * world = mManager->mPhysicsManager->mWorld;
	RayCastCallback callback;
	b2Vec2 pos = b2Vec2(origin.x, origin.y);
	b2Vec2 point1(pos.x, pos.y);
	b2Vec2 point2(target.x, target.y);

	world->RayCast(&callback, point1, point2);
	if (callback.mFixture == NULL)
	{
		return;
	}

	//We hit something.
	printf("ACTUAL HIT****");
	hr.entity = *(GameEntity**)(callback.mFixture->GetUserData());
	hr.contactPoint.x = callback.mPoint.x;
	hr.contactPoint.y = callback.mPoint.y;

	//XXX HR should be a property of the collision event.
	if ( hr.entity )
	{
		CollisionEventRec event;
		event.entity1 = hr.entity;
		event.mAction = COLLISION_EVENT_GUN_SHOT;
		event.point = glm::vec2(hr.contactPoint.x, hr.contactPoint.y);
		mManager->RegisterEvent(event);
	}

	ResetFireCounter();

	return;

}

ItemGameEntity::ItemGameEntity( const ItemGameEntityDef & inDef, GameManager * inMgr)
		:
		GameEntity(ENTITY_ITEM, inMgr)
	{
		SetPos(inDef.spawnPos);
		mType = inDef.type;
		mWorldWidth = inDef.worldWidth;
		mWorldHeight = inDef.worldHeight;
		//
		//Create the physics of the object.
		b2Vec2 topRight, topLeft;
		topRight.x = mPos.x + mWorldWidth/2;
		topRight.y = mPos.y + mWorldHeight/2;
		topLeft.x = mPos.x - mWorldWidth/2;
		topLeft.y = mPos.y + mWorldHeight/2;
		float thickness = mWorldHeight;
		
		//XXX
		//inDef.pp.mIsStatic = true;
		this->mPhysPrim = PhysicsFactory::CreateBox(topRight, topLeft, thickness, inDef.pp);

		mDisplayPrim = new SpriteDisplayPrimitive( &(inDef.dispDef) );
	}

void ItemGameEntity::Update( float ts ){
	//Just make sure the display primitive sprites are at this location
	mPos.x = this->mPhysPrim->GetPos().x;
	mPos.y = this->mPhysPrim->GetPos().y;
	this->mDisplayPrim->SetPos( this->GetPos() );	
	mDisplayPrim->SetAngle(this->mPhysPrim->GetAngle());
	
}

WeaponGameEntity * CreateWeapon( const WeaponProperties & inProps,
	GameManager * manager){
	WeaponGameEntity * retVal = NULL;
	switch(inProps.mType) {
	case(WEAPON_TYPE::WEAPON_TYPE_GUN):
		{
			retVal = new RaycastWeaponGameEntity(inProps, manager);
			break;
		}
	default:
	
		assert(false);
		break;
	};

	return retVal;
}